package com.tiger.service.websocket;

import com.alibaba.fastjson.JSON;
import com.tiger.utils.*;
import com.tiger.pojo.chat.MyMessage;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author HuXuehao (StudiousTiger)
 * @desc 这个类的作用是
 * @date 2022/3/17
 */
@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfigurator.class)
@Component
public class ChatEndpoint {
    //websocket中无法通过@Autowire和@Resource进行自动注入，需要通过手动获取bean
    private RedisUtils redisUtil = BeanUtils.getBean(RedisUtils.class);

    // static：因为这个类是所有用户共享的，使用 static 可以保证存储的数据不会被覆盖
    // ConcurrentHashMap: 考虑到线程安全，所有使用了它（并发编程）
    private static Map<String,ChatEndpoint> onlineUser = new ConcurrentHashMap<>();

    // 声明 websocket session对象，使得每一个用户拥有自己的独立的 session，因此我们不能使用 static
    private Session wsSession;

    // 用户取出当前用户存在 httpSession 中的信息
    private HttpSession httpSession;

    /**
     * 创建连接之后调用，与前端（客户端）的 ws.onopen 对应
     * @param session  websocket 的 session
     * @param endpointConfig endpoint 的配置
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig){
        try {
            // 给当前的 WebSocket Session 赋值
            this.wsSession = session;
            // 获取 httpSession，并存入当前的 httpSession
            this.httpSession = (HttpSession) endpointConfig.getUserProperties().get(HttpSession.class.getName());

            onlineUser.put((String) httpSession.getAttribute("userId"), this);

            // 将当前在线的用户（ID） “广播” 给所有人（判断用户在不在线）
            String message = MessageUtils.getMessage(true, null, onlineUser.keySet());
            // 调用自定义方法进行广播推送
            broadcastAllUser(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 广播所有在线的用户
    private void broadcastAllUser(String message){
        try {
            Set<String> UIDs = onlineUser.keySet();
            for (String UID : UIDs) {
                ChatEndpoint chatEndpoint = onlineUser.get(UID);
                chatEndpoint.wsSession.getBasicRemote().sendText(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 接收前端（客户端）发送的数据时调用，与前端（客户端）的 ws.onmessage 对应
     * @param message  前端（客户端）发送的消息（JSON => {toName:'',message:''}）
     * @param session  websocket 的 session
     */
    @OnMessage
    public void onMessage(String message, Session session){
        try {
            // 将JSON 转换成 Object
            MyMessage myMessage = JSON.parseObject(message, MyMessage.class);

            // 获取要发送的消息（JSON）
            String targetSendMessage = MessageUtils.getMessage(false,(String) httpSession.getAttribute("userId"), myMessage.getMessage());

            String listID = myMessage.getToName() + httpSession.getAttribute("userId");

            // 获取当前在线的用户集合
            Set<String> onlineUsers = onlineUser.keySet();
            // 若发送消息的目标在线，直接发送消息
            if (onlineUsers.contains(myMessage.getToName())) {
                // 发送消息
                onlineUser.get(myMessage.getToName())    // 获取目标用户对应的 ChatEndpoint 类
                        .wsSession   // 目标用户对应的 ChatEndpoint 类的成员变量
                        .getBasicRemote()  //
                        .sendText(targetSendMessage);  // 发送消息（targetSendMessage  : JSON）

            } else { // 发送消息的目标不在线
                // 获取以目标用户为主角的我们的聊天记录
                List<Object> chatRecordOfText = getChatRecordOfText(listID);
                List<Object> chatRecordOfDatetime = getChatRecordOfDatetime(listID);

                // 追加新的聊天记录
                List<Object> addChatRecordOfText = addChatRecordOfText(myMessage.getMessage(), chatRecordOfText);

                // 追加新的聊天时间
                List<Object> addChatRecordOfDatetime = addChatRecordOfDatetime(DateTimeUtils.currentDatetimeOfFormStr(), chatRecordOfDatetime);

                // 更新新的聊天记录（删除旧的聊天记录）
                saveNewChatRecordOfText(listID,addChatRecordOfText);
                saveNewChatRecordOfDatetime(listID,addChatRecordOfDatetime);

                // 设置新消息提示
                setNewNewsTips(listID);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private List<Object> getChatRecordOfText(String listID){
        try {
            return redisUtil.lGet(listID+"chattxt", 0, -1);
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }
    private List<Object> getChatRecordOfDatetime(String listID){
        try {
            return redisUtil.lGet(listID+"chatdatetime", 0L, -1L);
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }
    private List<Object> addChatRecordOfText(String massage, List<Object> listOfText){
        if ((listOfText.size()-1)%2 == 0){
            listOfText.add(massage);
            return listOfText;
        } else {
            listOfText.add("");
            listOfText.add(massage);
            return listOfText;
        }
    }
    private List<Object> addChatRecordOfDatetime(String datetime, List<Object> listOfDatetime){
        if ((listOfDatetime.size()-1)%2 == 0){
            listOfDatetime.add(datetime);
            return listOfDatetime;
        } else {
            listOfDatetime.add("");
            listOfDatetime.add(datetime);
            return listOfDatetime;
        }
    }
    private void saveNewChatRecordOfText(String listID, List<Object> listOfText){
        try {
            redisUtil.del(listID+"chattxt");
            for (Object res : listOfText) {
                redisUtil.lSet(listID+"chattxt", res.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private void saveNewChatRecordOfDatetime(String listID, List<Object> listOfDatetime){
        try {
            redisUtil.del(listID+"chatdatetime");
            for (Object res : listOfDatetime) {
                redisUtil.lSet(listID+"chatdatetime", res.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 设置新消息提示
     * @param mapKey  : toName + 当前登录用户Id组成
     */
    private static final String NEW_NEWS_MAP_NAME = "new_news_map_name";
    private void setNewNewsTips(String mapKey){
        String newNewsTips = (String)redisUtil.hget(NEW_NEWS_MAP_NAME, mapKey);
        int count = Integer.parseInt(newNewsTips);
        redisUtil.hset(NEW_NEWS_MAP_NAME,mapKey,count+1+"");
    }


    /**
     * 关闭连接之后调用，与前端（客户端）的 ws.onclose 对应
     * @param session  这个参数随意，我们可以根据自己的业务进行添加
     */
    @OnClose
    public void onClose(Session session){
        try {
            String userId = (String) httpSession.getAttribute("userId");
            onlineUser.remove(userId);
            String message = MessageUtils.getMessage(true, null, onlineUser.keySet());
            broadcastAllUser(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
